package com.gitee.hermer.engine.jars.app.sdk;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections.MultiHashMap;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import com.gitee.hermer.boot.jee.commons.collection.CollectionUtils;
import com.gitee.hermer.boot.jee.commons.collection.StringCache;
import com.gitee.hermer.boot.jee.commons.constants.OSType;
import com.gitee.hermer.boot.jee.commons.log.Logger;
import com.gitee.hermer.boot.jee.commons.log.Logger.Level;
import com.gitee.hermer.boot.jee.commons.utils.FileUtils;
import com.gitee.hermer.boot.jee.commons.utils.OSUtils;
import com.gitee.hermer.boot.jee.commons.utils.PathUtils;
import com.gitee.hermer.boot.jee.commons.utils.StringUtils;
import com.gitee.hermer.boot.jee.commons.verify.Assert;
import com.gitee.hermer.engine.jars.core.domain.EngineConstant;

import sun.misc.CompoundEnumeration;
import sun.misc.Resource;
import sun.misc.URLClassPath;




public class EngineAppClassLoader extends URLClassLoader{

	private Logger LOGGER = Logger.getLogger(EngineAppClassLoader.class);

	public static final String[] DEFAULT_EXCLUDED_PACKAGES = new String[]{"java.", "javax.", "sun.", "oracle."};

	/**
	 * 需要排除的包
	 */
	private final Set<String> excludedPackages = new HashSet<>();

	private MultiHashMap resources = null; // JAR中资源文件

	private final PathMatcher pathMatcher = new AntPathMatcher();

	private String filePath;
	private String cacheKey;


	public String getJarResourcesUrl(String resourcePath){
		if(StringUtils.isNotBlank(resourcePath)){
			if(resourcePath.startsWith(EngineConstant.PROTOCOL_JAR_FILE)){
				return resourcePath;
			}else{
				return new StringCache("jar:").append(getProtocolFileUrl(filePath)).append("!/").append(resourcePath).toString();
			}
		}
		return null;
	}

	public String getProtocolFileUrl(String filePath){
		Assert.hasText(filePath);
		if(OSUtils.getOSname() == OSType.Windows){
			return new StringCache(EngineConstant.PROTOCOL_WINDOWS_FILE).append(filePath).toString();
		}
		return new StringCache(EngineConstant.PROTOCOL_UNIX_FILE).append(filePath).toString();

	}

	public String getCacheKey() {
		return cacheKey;
	}
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof EngineAppClassLoader){
			return getCacheKey().equals(((EngineAppClassLoader)obj).getCacheKey());
		}
		return false;
	}

	public EngineAppClassLoader(URL[] urls, ClassLoader parent,String cacheKey,MultiHashMap resources) {
		this(urls, parent, null , cacheKey, resources);
	}

	public EngineAppClassLoader(URL[] urls, ClassLoader parent,String filePath,String cacheKey,MultiHashMap resources) {
		super(urls, parent);
		Assert.isTrue(!(parent instanceof EngineAppClassLoader));
		this.excludedPackages.addAll(CollectionUtils.newHashSet(DEFAULT_EXCLUDED_PACKAGES));
		this.cacheKey = cacheKey;
		this.resources = resources;
		this.filePath = filePath;
	}

	@Override
	protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
		Class<?> result = null;
		synchronized (EngineAppClassLoader.class) {
			if (isEligibleForOverriding(name)) {
				result = loadClassForOverriding(name, resolve);
			}
			if (result != null) {
				if (resolve) {
					resolveClass(result);
				}
				return result;
			}
		}
		return super.loadClass(name, resolve);
	}

	/**
	 * 判断该名字是否是需要覆盖的 class
	 *
	 * @param name
	 * @return
	 */
	private boolean isEligibleForOverriding(final String name) {
		Assert.notNull(name);
		return !isExcluded(name);
	}

	/**
	 * 判断class是否排除
	 *
	 * @param className
	 * @return
	 */
	protected boolean isExcluded(String className) {
		Assert.notNull(className);
		for (String packageName : this.excludedPackages) {
			if (className.startsWith(packageName)) {
				return true;
			}
		}
		return false;
	}

	private Class<?> loadClassForOverriding(String name, boolean resolve) throws ClassNotFoundException {
		Class<?> result = findLoadedClass(name);
		if (result == null) {
			try {
				result = findClass(name);
			} catch (ClassNotFoundException e) {
				result = super.loadClass(name, resolve);
			}
		}
		return result;
	}

	@Override
	public URL getResource(String name) {
		if(StringUtils.isNotEmpty(name) && isResourcesFile(name)){
			if(existSelfResource(name)){
				URL url = getResourceByAppExternalConfs(name);
				if(url == null){
					url = findResource(name);
					if(url == null){
						try{
							url = new URL(getJarResourcesUrl(name));
						}catch (Exception e) {
							LOGGER.error(e.getMessage(),e);
						}
					}
				}
				return url;
			}else if(name.startsWith(getAppExternalConfsPath().toString()) && !FileUtils.isExist(name)){
				try {
					String filePath = name.substring(getAppExternalConfsPath().toString().length());
					for (int i = 0; i < filePath.length(); i++) {
						char x = filePath.charAt(i);
						if((x>'a' &&x<'z' )||(x>'A'&&x<'Z') ){
							filePath = filePath.substring(i);
							break;
						}
					}
					return new URL(getJarResourcesUrl(filePath));
				} catch (MalformedURLException e) {
					e.printStackTrace();
				}
			}
		}
		return super.getResource(name);
	}

	protected boolean existSelfResource(String name){
		for (Object key : resources.keySet()) {
			Collection collection = resources.getCollection(key);
			for (Object object : collection) {
				String resource = (String) object;
				if(pathMatcher.match(name, resource)){
					return true;
				}
			}
		}
		if(getResourceByAppExternalConfs(name) != null)
			return true;
		LOGGER.info(StringUtils.format("资源{}文件不存在！将使用父容器配置.", name));
		return false;
	}

	protected StringCache getAppExternalConfsPath(){
		return new StringCache(PathUtils.webInfPath()).append(EngineConstant.FILE_NAME_ENGINE_WORK_CONF).append(File.separator)
				.append(filePath.substring(filePath.lastIndexOf(File.separator)+1, filePath.lastIndexOf(StringUtils.DOT))).append(File.separator);
	}

	protected URL getResourceByAppExternalConfs(String name){
		try{
			if(StringUtils.isNotBlank(filePath) && StringUtils.isNotBlank(name)){
				if(name.startsWith(getAppExternalConfsPath().toString()) && isResourcesFile(name)){
					return new URL(getProtocolFileUrl(name));
				}
				String path = "classes";
				if(name.indexOf(path) == -1){
					path = name;
				}else{
					path = name.substring(name.lastIndexOf(path)+path.length()+1);
				}
				File confs = new File(getAppExternalConfsPath().toString());
				if(confs.exists()){
					List<File> list = FileUtils.loopFiles(confs);
					for (File filePath : list) {
						String relativePath = null;
						if(OSUtils.isWindows()){
							relativePath = FileUtils.normalize(filePath.getPath().substring(getAppExternalConfsPath().toString().length()-1));
						}else{
							relativePath = FileUtils.normalize(filePath.getPath().substring(getAppExternalConfsPath().toString().length()));
						}
						if(filePath.isFile() && isResourcesFile(filePath.getPath()) && (relativePath.equals(path) || pathMatcher.match(path,relativePath))){
							URL url = new URL(getProtocolFileUrl(filePath.getPath()));
							LOGGER.info(StringUtils.format("引用外部{}配置，路径{}", path,url.toString()));
							return url;
						}
					}
				}
			}
		}catch (Exception e) {
			LOGGER.error(e.getMessage(),e);
		}
		return null;
	}

	protected boolean isResourcesFile(String name){
		return EngineConstant.DEFAULT_VALUES_ENGINE_APP_SCAN_RESOURCES.contains(FileUtils.getExtension(name));
	}

	@Override
	public Enumeration<URL> getResources(String name) throws IOException {
		@SuppressWarnings("unchecked")
		Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
		if (getParent() != null && !isResourcesFile(name)) {
			tmp[0] = getParent().getResources(name);
		} else {
			tmp[0] = getBootstrapResources(name);
		}
		tmp[1] = findResources(name);
		return new CompoundEnumeration<>(tmp);
	}

	private static Enumeration<URL> getBootstrapResources(String name)
			throws IOException
	{
		final Enumeration<Resource> e =
				getBootstrapClassPath().getResources(name);
		return new Enumeration<URL> () {
			public URL nextElement() {
				return e.nextElement().getURL();
			}
			public boolean hasMoreElements() {
				return e.hasMoreElements();
			}
		};
	}

	// Returns the URLClassPath that is used for finding system resources.
	static URLClassPath getBootstrapClassPath() {
		return sun.misc.Launcher.getBootstrapClassPath();
	}


}
